﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using Xamarin.Forms.Internals;

namespace Xamarin.Forms.Xaml.Internals
{
	public class XamlServiceProvider : IServiceProvider
	{
		readonly Dictionary<Type, object> services = new Dictionary<Type, object>();

		internal XamlServiceProvider(INode node, HydrationContext context)
		{
			object targetObject;
			if (node != null && node.Parent != null && context.Values.TryGetValue(node.Parent, out targetObject))
				IProvideValueTarget = new XamlValueTargetProvider(targetObject, node, context, null);
			if (context != null)
				IRootObjectProvider = new XamlRootObjectProvider(context.RootElement);
			if (node != null)
			{
				IXamlTypeResolver = new XamlTypeResolver(node.NamespaceResolver, XamlParser.GetElementType,
					context.RootElement.GetType().GetTypeInfo().Assembly);

				Add(typeof(IReferenceProvider), new ReferenceProvider(node));
			}

			var xmlLineInfo = node as IXmlLineInfo;
			if (xmlLineInfo != null)
				IXmlLineInfoProvider = new XmlLineInfoProvider(xmlLineInfo);

			IValueConverterProvider = new ValueConverterProvider();
		}

		public XamlServiceProvider()
		{
			IValueConverterProvider = new ValueConverterProvider();
		}

		internal IProvideValueTarget IProvideValueTarget
		{
			get { return (IProvideValueTarget)GetService(typeof (IProvideValueTarget)); }
			set { services[typeof (IProvideValueTarget)] = value; }
		}

		internal IXamlTypeResolver IXamlTypeResolver
		{
			get { return (IXamlTypeResolver)GetService(typeof (IXamlTypeResolver)); }
			set { services[typeof (IXamlTypeResolver)] = value; }
		}

		internal IRootObjectProvider IRootObjectProvider
		{
			get { return (IRootObjectProvider)GetService(typeof (IRootObjectProvider)); }
			set { services[typeof (IRootObjectProvider)] = value; }
		}

		internal IXmlLineInfoProvider IXmlLineInfoProvider
		{
			get { return (IXmlLineInfoProvider)GetService(typeof (IXmlLineInfoProvider)); }
			set { services[typeof (IXmlLineInfoProvider)] = value; }
		}

		[Obsolete]
		internal INameScopeProvider INameScopeProvider
		{
			get { return (INameScopeProvider)GetService(typeof (INameScopeProvider)); }
			set { services[typeof (INameScopeProvider)] = value; }
		}

		internal IValueConverterProvider IValueConverterProvider
		{
			get { return (IValueConverterProvider)GetService(typeof (IValueConverterProvider)); }
			set { services[typeof (IValueConverterProvider)] = value; }
		}

		public object GetService(Type serviceType)
		{
			object service;
			return services.TryGetValue(serviceType, out service) ? service : null;
		}

		public void Add(Type type, object service)
		{
			services.Add(type, service);
		}
	}

	class XamlValueTargetProvider : IProvideParentValues, IProvideValueTarget
	{
		public XamlValueTargetProvider(object targetObject, INode node, HydrationContext context, object targetProperty)
		{
			Context = context;
			Node = node;
			TargetObject = targetObject;
			TargetProperty = targetProperty;
		}

		INode Node { get; }

		HydrationContext Context { get; }
		public object TargetObject { get; }
		public object TargetProperty { get; internal set; } = null;

		IEnumerable<object> IProvideParentValues.ParentObjects
		{
			get
			{
				if (Node == null || Context == null)
					yield break;
				var n = Node;
				object obj = null;
				var context = Context;
				while (n.Parent != null && context != null)
				{
					if (n.Parent is IElementNode)
					{
						if (context.Values.TryGetValue(n.Parent, out obj))
							yield return obj;
						else
						{
							context = context.ParentContext;
							continue;
						}
					}
					n = n.Parent;
				}
			}
		}
	}

	public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarget, IReferenceProvider
	{
		readonly object[] objectAndParents;
		readonly object targetProperty;
		readonly INameScope scope;

		[Obsolete("SimpleValueTargetProvider(object[] objectAndParents) is obsolete as of version 2.3.4. Please use SimpleValueTargetProvider(object[] objectAndParents, object targetProperty) instead.")]
		public SimpleValueTargetProvider(object[] objectAndParents) : this (objectAndParents, null)
		{
		}

		[Obsolete("SimpleValueTargetProvider(object[] objectAndParents) is obsolete as of version 3.3.0. Please use SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, NameScope scope) instead.")]
		public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty) : this (objectAndParents, targetProperty, null)
		{
		}

		public SimpleValueTargetProvider(object[] objectAndParents, object targetProperty, INameScope scope)
		{
			if (objectAndParents == null)
				throw new ArgumentNullException(nameof(objectAndParents));
			if (objectAndParents.Length == 0)
				throw new ArgumentException();

			this.objectAndParents = objectAndParents;
			this.targetProperty = targetProperty;
			this.scope = scope;
		}

		IEnumerable<object> IProvideParentValues.ParentObjects
			=> objectAndParents;

		object IProvideValueTarget.TargetObject
			=> objectAndParents[0];

		object IProvideValueTarget.TargetProperty
			=> targetProperty;

		public object FindByName(string name)
		{
			if (scope != null)
				return scope.FindByName(name);

			for (var i = 0; i < objectAndParents.Length; i++) {
				var bo = objectAndParents[i] as BindableObject;
				if (bo == null) continue;
				var ns = NameScope.GetNameScope(bo) as INameScope;
				if (ns == null) continue;
				var value = ns.FindByName(name);
				if (value != null)
					return value;
			}
			return null;
		}
	}

	public class XamlTypeResolver : IXamlTypeResolver
	{
		readonly Assembly currentAssembly;
		readonly GetTypeFromXmlName getTypeFromXmlName;
		readonly IXmlNamespaceResolver namespaceResolver;

		public XamlTypeResolver(IXmlNamespaceResolver namespaceResolver, Assembly currentAssembly)
			: this(namespaceResolver, XamlParser.GetElementType, currentAssembly)
		{
		}

		internal XamlTypeResolver(IXmlNamespaceResolver namespaceResolver, GetTypeFromXmlName getTypeFromXmlName,
			Assembly currentAssembly)
		{
			this.currentAssembly = currentAssembly;
			if (namespaceResolver == null)
				throw new ArgumentNullException();
			if (getTypeFromXmlName == null)
				throw new ArgumentNullException();

			this.namespaceResolver = namespaceResolver;
			this.getTypeFromXmlName = getTypeFromXmlName;
		}

		Type IXamlTypeResolver.Resolve(string qualifiedTypeName, IServiceProvider serviceProvider)
		{
			XamlParseException e;
			var type = Resolve(qualifiedTypeName, serviceProvider, out e);
			if (e != null)
				throw e;
			return type;
		}

		bool IXamlTypeResolver.TryResolve(string qualifiedTypeName, out Type type)
		{
			XamlParseException exception;
			type = Resolve(qualifiedTypeName, null, out exception);
			return exception == null;
		}

		Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider, out XamlParseException exception)
		{
			exception = null;
			var split = qualifiedTypeName.Split(':');
			if (split.Length > 2)
				return null;

			string prefix, name;
			if (split.Length == 2)
			{
				prefix = split[0];
				name = split[1];
			}
			else
			{
				prefix = "";
				name = split[0];
			}

			IXmlLineInfo xmlLineInfo = null;
			if (serviceProvider != null)
			{
				var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
				if (lineInfoProvider != null)
					xmlLineInfo = lineInfoProvider.XmlLineInfo;
			}

			var namespaceuri = namespaceResolver.LookupNamespace(prefix);
			if (namespaceuri == null)
			{
				exception = new XamlParseException(string.Format("No xmlns declaration for prefix \"{0}\"", prefix), xmlLineInfo);
				return null;
			}

			return getTypeFromXmlName(new XmlType(namespaceuri, name, null), xmlLineInfo, currentAssembly, out exception);
		}

		internal delegate Type GetTypeFromXmlName(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly, out XamlParseException exception);
	}

	class XamlRootObjectProvider : IRootObjectProvider
	{
		public XamlRootObjectProvider(object rootObject)
		{
			RootObject = rootObject;
		}

		public object RootObject { get; }
	}

	public class XmlLineInfoProvider : IXmlLineInfoProvider
	{
		public XmlLineInfoProvider(IXmlLineInfo xmlLineInfo)
		{
			XmlLineInfo = xmlLineInfo;
		}

		public IXmlLineInfo XmlLineInfo { get; }
	}

	class ReferenceProvider : IReferenceProvider
	{
		readonly INode _node;
		internal ReferenceProvider(INode node)
			=> _node = node;

		public object FindByName(string name)
		{
			var n = _node;
			object value = null;
			while (n != null) {
				if ((value = (n as IElementNode)?.Namescope?.FindByName(name)) != null)
					return value;
				n = n.Parent;
			}
			return null;
		}
	}

	[Obsolete]
	public class NameScopeProvider : INameScopeProvider
	{
		public INameScope NameScope { get; set; }
	}

	public class XmlNamespaceResolver : IXmlNamespaceResolver
	{
		readonly Dictionary<string, string> namespaces = new Dictionary<string, string>();

		public IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope)
		{
			throw new NotImplementedException();
		}

		public string LookupNamespace(string prefix)
		{
			string result;
			if (namespaces.TryGetValue(prefix, out result))
				return result;
			return null;
		}

		public string LookupPrefix(string namespaceName)
		{
			throw new NotImplementedException();
		}

		public void Add(string prefix, string ns)
		{
			namespaces.Add(prefix, ns);
		}
	}
}